<style>
  .box {
    width: 100px;
    height: 100px;
    background-color: red;
  }
  .enter-active, .leave-active {
    transition: transform 1s ease-in-out;
  }
  .enter-from, .leave-to {
    transform: translateX(200px);
  }
  .enter-to, .leave-from {
    transform: translateX(0);
  }
  </style>
  
  <div id="app"></div>
  <div id="container"></div>
  
  <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
  <script>
  
  const { reactive, ref, effect, shallowReactive, shallowReadonly, shallowRef } = VueReactivity
  
  function lis(arr) {
    const p = arr.slice()
    const result = [0]
    let i, j, u, v, c
    const len = arr.length
    for (i = 0; i < len; i++) {
      const arrI = arr[i]
      if (arrI !== 0) {
        j = result[result.length - 1]
        if (arr[j] < arrI) {
          p[i] = j
          result.push(i)
          continue
        }
        u = 0
        v = result.length - 1
        while (u < v) {
          c = ((u + v) / 2) | 0
          if (arr[result[c]] < arrI) {
            u = c + 1
          } else {
            v = c
          }
        }
        if (arrI < arr[result[u]]) {
          if (u > 0) {
            p[i] = result[u - 1]
          }
          result[u] = i
        }
      }
    }
    u = result.length
    v = result[u - 1]
    while (u-- > 0) {
      result[u] = v
      v = p[v]
    }
    return result
  }
  
  function shouldSetAsProps(el, key, value) {
    if (key === 'form' && el.tagName === 'INPUT') return false
    return key in el
  }
  const Fragment = Symbol()
  

  function resolveProps(options, propsData) {
    const props = {}
    const attrs = {}
    for (const key in propsData) {
      if ((options && key in options) || key.startsWith('on')) {
        props[key] = propsData[key]
      } else {
        attrs[key] = propsData[key]
      }
    }

    return [ props, attrs ]
  }
  
  let currentInstance = null
  function setCurrentInstance(instance) {
    const prev = currentInstance
    currentInstance = instance
    return prev
  }
  
  function onMounted(fn) {
    if (currentInstance) {
      currentInstance.mounted.push(fn)
    }
  }
  
  function createRenderer(options) {
  
    const {
      createElement,
      insert,
      setElementText,
      patchProps,
      createText,
      setText
    } = options
  
    function mountElement(vnode, container, anchor) {
      const el = vnode.el = createElement(vnode.type)
  
      if (typeof vnode.children === 'string') {
        setElementText(el, vnode.children)
      } else if (Array.isArray(vnode.children)) {
        vnode.children.forEach(child => {
          patch(null, child, el)
        })
      }
  
      if (vnode.props) {
        for (const key in vnode.props) {
          patchProps(el, key, null, vnode.props[key])
        }
      }
  
      const needTransition = vnode.transition
      if (needTransition) {
        vnode.transition.beforeEnter(el)
      }
  
      insert(el, container, anchor)
      if (needTransition) {
        vnode.transition.enter(el)
      }
    }
  
    function patchChildren(n1, n2, container) {
      if (typeof n2.children === 'string') {
        if (Array.isArray(n1.children)) {
          n1.children.forEach((c) => unmount(c))
        }
        setElementText(container, n2.children)
      } else if (Array.isArray(n2.children)) {
        patchKeyedChildren(n1, n2, container)
      } else {
        if (Array.isArray(n1.children)) {
          n1.children.forEach(c => unmount(c))
        } else if (typeof n1.children === 'string') {
          setElementText(container, '')
        }
      }
    }
  
    function patchKeyedChildren(n1, n2, container) {
      const newChildren = n2.children
      const oldChildren = n1.children
      // 更新相同的前缀节点
      // 索引 j 指向新旧两组子节点的开头
      let j = 0
      let oldVNode = oldChildren[j]
      let newVNode = newChildren[j]
      const min = Math.min(oldChildren.length, newChildren.length)
      // while 循环向后遍历，直到遇到拥有不同 key 值的节点为止
      while (j < min && oldVNode.key === newVNode.key) {
        // 调用 patch 函数更新
        patch(oldVNode, newVNode, container)
        j++
        oldVNode = oldChildren[j]
        newVNode = newChildren[j]
      }
  
      // 更新相同的后缀节点
      // 索引 oldEnd 指向旧的一组子节点的最后一个节点
      let oldEnd = oldChildren.length - 1
      // 索引 newEnd 指向新的一组子节点的最后一个节点
      let newEnd = newChildren.length - 1
  
      oldVNode = oldChildren[oldEnd]
      newVNode = newChildren[newEnd]
  
      // while 循环向前遍历，直到遇到拥有不同 key 值的节点为止
      while (oldEnd > 0 && newEnd > 0 && oldVNode.key === newVNode.key) {
        // 调用 patch 函数更新
        patch(oldVNode, newVNode, container)
        oldEnd--
        newEnd--
        oldVNode = oldChildren[oldEnd]
        newVNode = newChildren[newEnd]
      }
  
      // 满足条件，则说明从 j -> newEnd 之间的节点应作为新节点插入
      if (j > oldEnd && j <= newEnd) {
        // 锚点的索引
        const anchorIndex = newEnd + 1
        // 锚点元素
        const anchor = anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null
        // 采用 while 循环，调用 patch 函数逐个挂载新增的节点
        while (j <= newEnd) {
          patch(null, newChildren[j++], container, anchor)
        }
      } else if (j > newEnd && j <= oldEnd) {
        // j -> oldEnd 之间的节点应该被卸载
        while (j <= oldEnd) {
          unmount(oldChildren[j++])
        }
      } else {
        // 构造 source 数组
        const count = newEnd - j + 1  // 新的一组子节点中剩余未处理节点的数量
        if (count <= 0) return
        const source = new Array(count)
        source.fill(-1)
  
        const oldStart = j
        const newStart = j
        let moved = false
        let pos = 0
        const keyIndex = {}
        for(let i = newStart; i <= newEnd; i++) {
          keyIndex[newChildren[i].key] = i
        }
        let patched = 0
        for(let i = oldStart; i <= oldEnd; i++) {
          oldVNode = oldChildren[i]
          if (patched < count) {
            const k = keyIndex[oldVNode.key]
            if (typeof k !== 'undefined') {
              newVNode = newChildren[k]
              patch(oldVNode, newVNode, container)
              patched++
              source[k - newStart] = i
              // 判断是否需要移动
              if (k < pos) {
                moved = true
              } else {
                pos = k
              }
            } else {
              // 没找到
              unmount(oldVNode)
            }
          } else {
            unmount(oldVNode)
          }
        }
  
        if (moved) {
          const seq = lis(source)
          // s 指向最长递增子序列的最后一个值
          let s = seq.length - 1
          let i = count - 1
          for (i; i >= 0; i--) {
            if (source[i] === -1) {
              // 说明索引为 i 的节点是全新的节点，应该将其挂载
              // 该节点在新 children 中的真实位置索引
              const pos = i + newStart
              const newVNode = newChildren[pos]
              // 该节点下一个节点的位置索引
              const nextPos = pos + 1
              // 锚点
              const anchor = nextPos < newChildren.length
                ? newChildren[nextPos].el
                : null
              // 挂载
              patch(null, newVNode, container, anchor)
            } else if (i !== seq[j]) {
              // 说明该节点需要移动
              // 该节点在新的一组子节点中的真实位置索引
              const pos = i + newStart
              const newVNode = newChildren[pos]
              // 该节点下一个节点的位置索引
              const nextPos = pos + 1
              // 锚点
              const anchor = nextPos < newChildren.length
                ? newChildren[nextPos].el
                : null
              // 移动
              insert(newVNode.el, container, anchor)
            } else {
              // 当 i === seq[j] 时，说明该位置的节点不需要移动
              // 并让 s 指向下一个位置
              s--
            }
          }
        }
      }
  
    }
  
    function patchElement(n1, n2) {
      const el = n2.el = n1.el
      const oldProps = n1.props
      const newProps = n2.props
      
      for (const key in newProps) {
        if (newProps[key] !== oldProps[key]) {
          patchProps(el, key, oldProps[key], newProps[key])
        }
      }
      for (const key in oldProps) {
        if (!(key in newProps)) {
          patchProps(el, key, oldProps[key], null)
        }
      }
  
      patchChildren(n1, n2, el)
    }
  
    function unmount(vnode) {
      const needTransition = vnode.transition
      if (vnode.type === Fragment) {
        vnode.children.forEach(c => unmount(c))
        return
      } else if (typeof vnode.type === 'object') {
        if (vnode.shouldKeepAlive) {
          vnode.keepAliveInstance._deActivate(vnode)
        } else {
          unmount(vnode.component.subTree)
        }
        return
      }
      const parent = vnode.el.parentNode
      if (parent) {
        const performRemove = () => parent.removeChild(vnode.el)
        if (needTransition) {
          vnode.transition.leave(vnode.el, performRemove)
        } else {
          performRemove()
        }
      }
    }
  
    function patch(n1, n2, container, anchor) {
      if (n1 && n1.type !== n2.type) {
        unmount(n1)
        n1 = null
      }
  
      const { type } = n2
  
      if (typeof type === 'string') {
        if (!n1) {
          mountElement(n2, container, anchor)
        } else {
          patchElement(n1, n2)
        }
      } else if (type === Text) {
        if (!n1) {
          const el = n2.el = createText(n2.children)
          insert(el, container)
        } else {
          const el = n2.el = n1.el
          if (n2.children !== n1.children) {
            setText(el, n2.children)
          }
        }
      } else if (type === Fragment) {
        if (!n1) {
          n2.children.forEach(c => patch(null, c, container))
        } else {
          patchChildren(n1, n2, container)
        }
      } else if (typeof type === 'object' && type.__isTeleport) {
        type.process(n1, n2, container, anchor, {
          patch,
          patchChildren,
          move(vnode, container, anchor) {
            insert(vnode.component ? vnode.component.subTree.el : vnode.el, container, anchor)
          }
        })
      } else if (typeof type === 'object' || typeof type === 'function') {
        // component
        if (!n1) {
          if (n2.keptAlive) {
            n2.keepAliveInstance._activate(n2, container, anchor)
          } else {
            mountComponent(n2, container, anchor)
          }
        } else {
          patchComponent(n1, n2, anchor)
        }
      }
    }
  
    function mountFunctionalComponent(n2, container, anchor) {
      
    }
  
    function patchComponent(n1, n2, anchor) {
      const instance = (n2.component = n1.component)
      const { props } = instance
      if (hasPropsChanged(n1.props, n2.props)) {
        const [ nextProps, nextAttrs ] = resolveProps(n2.type.props, n2.props)
        for (const k in nextProps) {
          props[k] = nextProps[k]
        }
        for (const k in props) {
          if (!(k in nextProps)) delete props[k]
        }
  
  
  
        instance.update()
      }
    }
  
    function hasPropsChanged(
      prevProps,
      nextProps
    ) {
      if (prevProps === nextProps) return false
      const nextKeys = Object.keys(nextProps)
      if (nextKeys.length !== Object.keys(prevProps).length) {
        return true
      }
      for (let i = 0; i < nextKeys.length; i++) {
        const key = nextKeys[i]
        return nextProps[key] !== prevProps[key]
      }
      return false
    }
  
    const p = Promise.resolve()
    const queue = new Set()
    let isFlushing = false
    function queueJob(job) {
      queue.add(job)
      if (!isFlushing) {
        isFlushing = true
        p.then(() => {
          try {
            queue.forEach(jon => job())
          } finally {
            isFlushing = false
          }
        })
      }
    }
  
    function mountComponent(vnode, container, anchor) {
      const isFunctional = typeof vnode.type === 'function'
      let componentOptions = vnode.type
      if (isFunctional) {
        componentOptions = {
          render: vnode.type,
          props: vnode.type.props
        }
      }
      let { render, data, setup, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption } = componentOptions

      beforeCreate && beforeCreate()

      const state = data ? reactive(data()) : null
      const [props, attrs] = resolveProps(propsOption, vnode.props)

      const slots = vnode.children || {}

      const instance = {
        state,
        props: shallowReactive(props),
        isMounted: false,
        subTree: null,
        slots,
        mounted: [],
        keepAliveCtx: null
      }

      const isKeepAlive = vnode.type.__isKeepAlive
      if (isKeepAlive) {
        instance.keepAliveCtx = {
          move(vnode, container, anchor) {
            insert(vnode.component.subTree.el, container, anchor)
          },
          createElement
        }
      }

      function emit(event, ...payload) {
        const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
        const handler = instance.props[eventName]
        if (handler) {
          handler(...payload)
        } else {
          console.error('事件不存在')
        }
      }

      // setup
      let setupState = null
      if (setup) {
        const setupContext = { attrs, emit, slots }
        const prevInstance = setCurrentInstance(instance)
        const setupResult = setup(shallowReadonly(instance.props), setupContext)
        setCurrentInstance(prevInstance)
        if (typeof setupResult === 'function') {
          if (render) console.error('setup 函数返回渲染函数，render 选项将被忽略')
          render = setupResult
        } else {
          setupState = setupContext
        }
      }

      vnode.component = instance

      const renderContext = new Proxy(instance, {
        get(t, k, r) {
          const { state, props, slots } = t

          if (k === '$slots') return slots

          if (state && k in state) {
            return state[k]
          } else if (k in props) {
            return props[k]
          } else if (setupState && k in setupState) {
            return setupState[k]
          } else {
            console.error('不存在')
          }
        },
        set (t, k, v, r) {
          const { state, props } = t
          if (state && k in state) {
            state[k] = v
          } else if (k in props) {
            props[k] = v
          } else if (setupState && k in setupState) {
            setupState[k] = v
          } else {
            console.error('不存在')
          }
        }
      })

      // created
      created && created.call(renderContext)


      instance.update = effect(() => {
        const subTree = render.call(renderContext, renderContext)
        if (!instance.isMounted) {
          beforeMount && beforeMount.call(renderContext)
          if (vnode.el) {
            console.log(999)
            hydrateNode(vnode.el, subTree)
          } else {
            patch(null, subTree, container, anchor)
          }
          instance.isMounted = true
          mounted && mounted.call(renderContext)
          instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
        } else {
          beforeUpdate && beforeUpdate.call(renderContext)
          patch(instance.subTree, subTree, container, anchor)
          updated && updated.call(renderContext)
        }
        instance.subTree = subTree
      }, {
        scheduler: queueJob
      })
    }
  
    function render(vnode, container) {
      if (vnode) {
        // 新 vnode 存在，将其与旧 vnode 一起传递给 patch 函数进行打补丁
        patch(container._vnode, vnode, container)
      } else {
        if (container._vnode) {
          // 旧 vnode 存在，且新 vnode 不存在，说明是卸载(unmount)操作
          unmount(container._vnode)
        }
      }
      // 把 vnode 存储到 container._vnode 下，即后续渲染中的旧 vnode
      container._vnode = vnode
    }

    function hydrate(vnode, container) {
      hydrateNode(container.firstChild, vnode)
    }

    function hydrateNode(node, vnode) {
      const { type } = vnode
      vnode.el = node

      if (typeof type === 'object') {
        mountComponent(vnode, container, null)
      } else if (typeof type === 'string') {
        hydrateElement(node, vnode)
      }

      return node.nextSibling
    }

    function hydrateElement(el, vnode) {
      if (vnode.props) {
        for (const key in vnode.props) {
          if (/^on/.test(key)) {
            patchProps(el, key, null, vnode.props[key])
          }
        }
      }
      if (Array.isArray(vnode.children)) {
        let nextNode = el.firstChild
        const len = vnode.children.length
        for (let i = 0; i < len; i++) {
          nextNode = hydrateNode(nextNode, vnode.children[i])
        }
      }
    }
    
    return {
      render,
      hydrate
    }
  }
  
  const renderer = createRenderer({
    createElement(tag) {
      return document.createElement(tag)
    },
    setElementText(el, text) {
      el.textContent = text
    },
    insert(el, parent, anchor = null) {
      parent.insertBefore(el, anchor)
    },
    createText(text) {
      return document.createTextNode(text)
    },
    setText(el, text) {
      el.nodeValue = text
    },
    patchProps(el, key, prevValue, nextValue) {
      if (/^on/.test(key)) {
        const invokers = el._vei || (el._vei = {})
        let invoker = invokers[key]
        const name = key.slice(2).toLowerCase()
        if (nextValue) {
          if (!invoker) {
            invoker = el._vei[key] = (e) => {
              if (e.timeStamp < invoker.attached) return
              if (Array.isArray(invoker.value)) {
                invoker.value.forEach(fn => fn(e))
              } else {
                invoker.value(e)
              }
            }
            invoker.value = nextValue
            invoker.attached = performance.now()
            el.addEventListener(name, invoker)
          } else {
            invoker.value = nextValue
          }
        } else if (invoker) {
          el.removeEventListener(name, invoker)
        }
      } else if (key === 'class') {
        el.className = nextValue || ''
      } else if (shouldSetAsProps(el, key, nextValue)) {
        const type = typeof el[key]
        if (type === 'boolean' && nextValue === '') {
          el[key] = true
        } else {
          el[key] = nextValue
        }
      } else {
        el.setAttribute(key, nextValue)
      }
    }
  })
  
  const cache = new Map()
  const KeepAlive = {
    __isKeepAlive: true,
    props: {
      include: RegExp,
      exclude: RegExp
    },
    setup(props, { slots }) {
      
      const instance = currentInstance
      const { move, createElement } = instance.keepAliveCtx
  
      const storageContainer = createElement('div')
  
      instance._deActivate = (vnode) => {
        move(vnode, storageContainer)
      }
  
      instance._activate = (vnode, container, anchor) => {
        move(vnode, container, anchor)
      }
  
      return () => {
        let rawVNode = slots.default()
        if (typeof rawVNode.type !== 'object') {
          return rawVNode
        }
  
        const name = rawVNode.type.name
        if (
          name &&
          (
            (props.include && !props.include.test(name)) ||
            (props.exclude && props.exclude.test(name))
          )
        ) {
          return rawVNode
        }
  
        const cachedVNode = cache.get(rawVNode.type)
        if (cachedVNode) {
          rawVNode.component = cachedVNode.component
          rawVNode.keptAlive = true
        } else {
          cache.set(rawVNode.type, rawVNode)
        }
  
        rawVNode.shouldKeepAlive = true
        rawVNode.keepAliveInstance = instance
  
        return rawVNode
      }
    }
  }
  
  const Teleport = {
    __isTeleport: true,
    process(n1, n2, container, anchor, internals) {
      const { patch, patchChildren, move } = internals
      if (!n1) {
        // 挂载
        const target = typeof n2.props.to === 'string'
          ? document.querySelector(n2.props.to)
          : n2.props.to
        n2.children.forEach(c => patch(null, c, target, anchor))
      } else {
        // 更新
        patchChildren(n1, n2, container)
        if (n2.props.to !== n1.props.to) {
          const newTarget = typeof n2.props.to === 'string'
            ? document.querySelector(n2.props.to)
            : n2.props.to
          n2.children.forEach(c => move(c, newTarget))
        }
      }
    }
  }
  
  const Transition = {
    name: 'Transition',
    setup(props, { slots }) {
      return () => {
        const innerVNode = slots.default()
  
        innerVNode.transition = {
          beforeEnter(el) {
            el.classList.add('enter-from')
            el.classList.add('enter-active')
          },
          enter(el) {
            nextFrame(() => {
              el.classList.remove('enter-from')
              el.classList.add('enter-to')
  
              el.addEventListener('transitionend', () => {
                el.classList.remove('enter-to')
                el.classList.remove('enter-active')
              })
            })
          },
          leave(el, performRemove) {
            el.classList.add('leave-from')
            el.classList.add('leave-active')
  
            // document.body.offsetHeight
  
            nextFrame(() => {
              el.classList.remove('leave-from')
              el.classList.add('leave-to')
  
              el.addEventListener('transitionend', () => {
                el.classList.remove('leave-to')
                el.classList.remove('leave-active')
  
                performRemove()
              })
            })
          }
        }
  
        return innerVNode
      }
    }
  }

  // renderer.render({ type: App }, document.querySelector('#app'))
  
  
  function nextFrame(cb) {
    requestAnimationFrame(() => {
      requestAnimationFrame(cb)
    })
  }


  // server render ----------------------------------------------------------



  const VOID_TAGS = 'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr'.split(',')
  function renderElementVNode(vnode) {
    const { type: tag, props, children } = vnode
    const isVoidElement = VOID_TAGS.includes(tag)

    let ret = `<${tag}`

    if (props) {
      ret += renderAttrs(props)
    }

    ret += isVoidElement ? `/>` : `>`
    
    if (isVoidElement) return ret
    
    if (typeof children === 'string') {
      ret += children
    } else {
      children.forEach(child => {
        ret += renderElementVNode(child)
      })
    }

    ret += `</${tag}>`

    return ret
  }


  const shouldIgnoreProp = ['key', 'ref']
  function renderAttrs(props) {
    let ret = ''
    for (const key in props) {
      if (
        shouldIgnoreProp.includes(key) ||
        /^on[^a-z]/.test(key)
      ) {
        continue
      }
      const value = props[key]
      if (key === 'class') {
        // ret += ` class="${renderClass(value)}"`
      } else if (key === 'style') {
        // ret += ` style="${renderStyle(value)}"`
      } else {
        ret += renderDynamicAttr(key, value)
      }
    }
    return ret
  }

  function isRenderableValue(value) {
    if (value == null) {
      return false
    }
    const type = typeof value
    return type === 'string' || type === 'number' || type === 'boolean'
  }
  
  const isBooleanAttr = (key) => (`itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly` +
    `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,` +
    `loop,open,required,reversed,scoped,seamless,` +
    `checked,muted,multiple,selected`).split(',').includes(key)
  const isSSRSafeAttrName = (key) => true
  function renderDynamicAttr(key, value) {
    if (!isRenderableValue(value)) {
      return ``
    }
    if (isBooleanAttr(key)) {
      return value === false ? `` : ` ${key}`
    } else if (isSSRSafeAttrName(key)) {
      return value === '' ? ` ${key}` : ` ${key}="${escapeHtml(value)}"`
    } else {
      console.warn(
        `[@vue/server-renderer] Skipped rendering unsafe attribute name: ${key}`
      )
      return ``
    }
  }
  
  const escapeRE = /["'&<>]/
  function escapeHtml(string) {
    const str = '' + string
    const match = escapeRE.exec(str)

    if (!match) {
      return str
    }

    let html = ''
    let escaped
    let index
    let lastIndex = 0
    for (index = match.index; index < str.length; index++) {
      switch (str.charCodeAt(index)) {
        case 34: // "
          escaped = '&quot;'
          break
        case 38: // &
          escaped = '&amp;'
          break
        case 39: // '
          escaped = '&#39;'
          break
        case 60: // <
          escaped = '&lt;'
          break
        case 62: // >
          escaped = '&gt;'
          break
        default:
          continue
      }

      if (lastIndex !== index) {
        html += str.substring(lastIndex, index)
      }

      lastIndex = index + 1
      html += escaped
    }

    return lastIndex !== index ? html + str.substring(lastIndex, index) : html
  }


  const ElementVNode = {
    type: 'input',
    props: {
      id: 'foo',
      checked: ""
    },
    children: [
      { type: 'hr' }
    ]
  }

  const MyComponent = {
    name: 'App',
    setup() {
      const str = ref('foo')

      return () => {
        return {
          type: 'div',
          children: [
            {
              type: 'span',
              children: str.value,
              props: {
                onClick: () => {
                  str.value = 'bar'
                }
              }
            },
            { type: 'span', children: 'baz' }
          ]
        }
      }
    }
  }

  const compVNode = {
    type: MyComponent,
  }

  function renderToString(vnode) {
    
  }

  function renderComponentVNode(vnode) {
    const isFunctional = typeof vnode.type === 'function'
    let componentOptions = vnode.type
    if (isFunctional) {
      componentOptions = {
        render: vnode.type,
        props: vnode.type.props
      }
    }
    let { render, data, setup, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption } = componentOptions

    beforeCreate && beforeCreate()

    const state = data ? data() : null
    const [props, attrs] = resolveProps(propsOption, vnode.props)

    const slots = vnode.children || {}

    const instance = {
      state,
      props,
      isMounted: false,
      subTree: null,
      slots,
      mounted: [],
      keepAliveCtx: null
    }

    function emit(event, ...payload) {
      const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
      const handler = instance.props[eventName]
      if (handler) {
        handler(...payload)
      } else {
        console.error('事件不存在')
      }
    }

    // setup
    let setupState = null
    if (setup) {
      const setupContext = { attrs, emit, slots }
      const prevInstance = setCurrentInstance(instance)
      const setupResult = setup(shallowReadonly(instance.props), setupContext)
      setCurrentInstance(prevInstance)
      if (typeof setupResult === 'function') {
        if (render) console.error('setup 函数返回渲染函数，render 选项将被忽略')
        render = setupResult
      } else {
        setupState = setupContext
      }
    }

    vnode.component = instance

    const renderContext = new Proxy(instance, {
      get(t, k, r) {
        const { state, props, slots } = t

        if (k === '$slots') return slots

        if (state && k in state) {
          return state[k]
        } else if (k in props) {
          return props[k]
        } else if (setupState && k in setupState) {
          return setupState[k]
        } else {
          console.error('不存在')
        }
      },
      set (t, k, v, r) {
        const { state, props } = t
        if (state && k in state) {
          state[k] = v
        } else if (k in props) {
          props[k] = v
        } else if (setupState && k in setupState) {
          setupState[k] = v
        } else {
          console.error('不存在')
        }
      }
    })

    // created
    created && created.call(renderContext)

    const subTree = render.call(renderContext, renderContext)

    return renderVNode(subTree)
  }

  function renderVNode(vnode) {
    const type = typeof vnode.type
    if (type === 'string') {
      return renderElementVNode(vnode)
    } else if (type === 'object' || type === 'function') {
      return renderComponentVNode(vnode)
    } else if (vnode.type === Text) {
      // 处理文本...
    } else if (vnode.type === Fragment) {
      // 处理片段...
    } else {
      // 其他 VNode 类型
    }
  }

  const container = document.querySelector('#app')
  const html = renderComponentVNode(compVNode)
  container.innerHTML = html


  renderer.hydrate(compVNode, container)


  
  </script>